/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> uls_istream.c </file>
  <brief>
  	csz: C String terminated by Zero.
    Routines for dynamically manipulating infinite c-string.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, 2011.
  </author>
*/

#include "common.h"
#include "csz_stream.h"
#include "uls_lf_sprintf.h"
#include "uls_log.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define N_LINES_IN_POOL  16

static DECLARE_ULS_MUTEX(csz_global_mtx);
static uls_char_t *lines_in_pool[N_LINES_IN_POOL];
static int  siz_lines_in_pool[N_LINES_IN_POOL];
static int  n_lines_in_pool;

static int
__str_modify(uls_char_t** p_outbuf, int* p_outsiz, int n_delta, int k, const uls_char_t* str, int len);
static void reset_csz(void);

static uls_char_t*
__find_in_pool(int siz, int* p_ret_siz)
{
	uls_char_t *line0;
	int i, i0 = -1, last_selected=0;

	if (siz == 0) {
		*p_ret_siz = 0;
		return NULL;
	}

	if (siz < 0) {
		for (i=n_lines_in_pool-1; i>=0; i--) {
			if (lines_in_pool[i] != NULL) {
				i0 = i;
				break;
			}
		}

		last_selected = 1;

	} else {
		for (i=0; ; i++) {
			if (i >= n_lines_in_pool) {
				last_selected = 1;
				break;
			}

			if (lines_in_pool[i] == NULL)
				continue;

			if (siz > siz_lines_in_pool[i]) {
				break;
			} else {
				i0 = i;
			}
		}
	}

	if (i0 >= 0) {
		if (last_selected) {
			for (i=i0-1; i>=0; i--) {
				if (lines_in_pool[i] != NULL)
					break;
			}
			n_lines_in_pool = i + 1;
		}

		line0 = lines_in_pool[i0];
		*p_ret_siz = siz_lines_in_pool[i0];

		lines_in_pool[i0] = NULL;
		siz_lines_in_pool[i0] = 0;

	} else {
		line0 = NULL;
		*p_ret_siz = 0;
	}

	return line0;
}

static int
__find_empty_slot_in_pool(int req_siz)
{
	int i, j, siz, i_empty = -1;

	for (i=0; i<n_lines_in_pool; i++) {
		if ((siz=siz_lines_in_pool[i]) > 0) {
			if (req_siz >= siz) {
				if (i_empty < 0) {
					for (j=i+1; j<n_lines_in_pool; j++) {
						if (siz_lines_in_pool[j] == 0) {
							i_empty = j;
							break;
						}
					}
				}
				break;
			}
		} else { // siz == 0
			i_empty = i; // the recent empty slot
		}
	}

	// i_empty : a nearest empty slot
	if (i_empty >= 0) { // i_empty, ..., i
		// assert: i_empty != i
		if (i_empty < i) {
			--i;
			for (j=i_empty; j<i; j++) {
				// shift left by 1
				lines_in_pool[j] = lines_in_pool[j+1];
				siz_lines_in_pool[j] = siz_lines_in_pool[j+1];
			}
		} else { // i < i_empty
			for (j=i; j<i_empty; j++) {
				lines_in_pool[j+1] = lines_in_pool[j];
				siz_lines_in_pool[j+1] = siz_lines_in_pool[j];
			}
		}

	} else if (n_lines_in_pool < N_LINES_IN_POOL) {
		if (i < n_lines_in_pool) {
			for (j=n_lines_in_pool-1; j>=i; j--) {
				lines_in_pool[j+1] = lines_in_pool[j];
				siz_lines_in_pool[j+1] = siz_lines_in_pool[j];
			}
		}
		++n_lines_in_pool;

	} else {
		i = -1;
	}

	return i;
}

void csz_init(csz_str_t* ss, int n_delta)
{
	if (ss==NULL) {
		reset_csz();
		return;
	}

	ss->pool = NULL;
	ss->pool_size = 0;

	if (n_delta <= 0) {
		n_delta = CSZ_STREAM_DELTA_DFL;
	}

	ss->alloc_delta = uls_roundup(n_delta, 8);
	// ss->alloc_delta % 8 == 0

	ss->len = 0;
}

void csz_deinit(csz_str_t* ss)
{
	str_free(&ss->pool, &ss->pool_size);
	ss->len = 0;
}

uls_char_t* csz_text(csz_str_t* ss)
{
	__str_putc(&ss->pool, &ss->pool_size, ss->alloc_delta, ss->len, '\0');
	return ss->pool;
}

void csz_add_eos(csz_str_t* ss)
{
	__str_putc(&ss->pool, &ss->pool_size, ss->alloc_delta, ss->len, '\0');
	++ss->len;
}

void csz_print(csz_str_t* ss)
{
	printf("%s", csz_text(ss));
}

void csz_putc(csz_str_t* ss, uls_char_t ch)
{
	__str_putc(&ss->pool, &ss->pool_size, ss->alloc_delta, ss->len, ch);
	if (ch != '\0') ++ss->len;
}

void csz_puts(csz_str_t* ss, uls_char_t* str)
{
	int k = ss->len;

	k = __str_modify(&ss->pool, &ss->pool_size, ss->alloc_delta, k, str, -1);
	__str_putc(&ss->pool, &ss->pool_size, ss->alloc_delta, k, '\0');
	ss->len = k;
}

void csz_append(csz_str_t* ss, uls_char_t* str, int len)
{
	if (len < 0) {
		csz_puts(ss, str);
	} else {
		ss->len = __str_modify(&ss->pool, &ss->pool_size, ss->alloc_delta, ss->len, str, len);
	}
}

void csz_modify(csz_str_t* ss, int k, uls_char_t* str, int len)
{
	if (len > 0) {
		k = __str_modify(&ss->pool, &ss->pool_size, ss->alloc_delta, k, str, len);
		if (k > ss->len) ss->len = k;
	}
}

void
__str_putc(uls_char_t** p_outbuf, int* p_outsiz, int n_delta, int k, uls_char_t ch)
{
	uls_char_t *outbuf = *p_outbuf;

	if (outbuf == NULL) {
		*p_outsiz = 0;
	}

	if (k < 0 || k+1 <= 0)
		err_panic("too big buff?!");

	if (k >= *p_outsiz) {
		*p_outsiz = uls_roundup(k+1, n_delta);
		*p_outbuf = outbuf = uls_mrealloc(outbuf, *p_outsiz*sizeof(uls_char_t));
	}

	outbuf[k] = ch;
}

static int
__str_modify(uls_char_t** p_outbuf, int* p_outsiz, int n_delta, int k, const uls_char_t* str, int len)
{
	uls_char_t *outbuf = *p_outbuf;
	int  n = 0;

	if (len < 0) {
		len = (str != NULL) ? uls_strlen(str) : 0;
	}
	// len >= 0

	if (k < 0 || (n=k+len+1) <= 0)
		err_panic("too big buff?!");

	if (n > *p_outsiz) {
		n = uls_roundup(n, n_delta);
		*p_outbuf = outbuf = uls_mrealloc(outbuf, n*sizeof(uls_char_t));
		*p_outsiz = n;
	}

	// The assignment into outbuf[k+len] is valid 
	// as k+len < *p_outsiz

	if (str != NULL && len > 0) {
		memcpy(outbuf+k, str, len);
		k += len;
	}

	return k;
}

int
str_puts(uls_char_t** p_outbuf, int* p_outsiz, int k, const uls_char_t* str)
{
	k = __str_modify(p_outbuf, p_outsiz, CSZ_STREAM_DELTA_DFL, k, str, -1);
	__str_putc(p_outbuf, p_outsiz, CSZ_STREAM_DELTA_DFL, k, '\0');

	return k;
}

int
str_modify(uls_char_t** p_outbuf, int* p_outsiz, int k, const uls_char_t* str, int len)
{
	if (len < 0) {
		k = str_puts(p_outbuf, p_outsiz, k, str);
	} else {
		k = __str_modify(p_outbuf, p_outsiz, CSZ_STREAM_DELTA_DFL, k, str, len);
	}

	return k;
}

void
str_init(uls_char_t** p_outbuf, int* p_outsiz, int siz)
{
	if (siz == 0) {
		*p_outbuf = NULL;
		*p_outsiz = 0;
		return;
	}

	if (siz > 0) {
		siz = uls_roundup(siz, 8);
	}

	uls_lock_mutex(&csz_global_mtx);
	*p_outbuf = __find_in_pool(siz, p_outsiz);
	uls_unlock_mutex(&csz_global_mtx);

	if (*p_outbuf == NULL) {
		*p_outbuf = uls_malloc(siz);
		*p_outsiz = siz;
	}
}

void
str_free(uls_char_t** p_outbuf, int* p_outsiz)
{
	int i;
	uls_char_t *line = *p_outbuf;

	if (line != NULL) {
		uls_lock_mutex(&csz_global_mtx);
		if ((i=__find_empty_slot_in_pool(*p_outsiz)) >= 0) {
			lines_in_pool[i] = line;
			siz_lines_in_pool[i] = *p_outsiz;
			line = NULL;
		}
		uls_unlock_mutex(&csz_global_mtx);
		uls_mfree(line);
	}

	*p_outbuf = NULL;
	*p_outsiz = 0;
}

static void reset_csz(void)
{
	int i;
	uls_char_t *line;

	uls_lock_mutex(&csz_global_mtx);

	for (i=0; i<N_LINES_IN_POOL; i++) {
		if ((line=lines_in_pool[i]) != NULL) {
			lines_in_pool[i] = NULL;
			siz_lines_in_pool[i] = 0;
			uls_mfree(line);
		}
	}

	uls_unlock_mutex(&csz_global_mtx);
}

void initialize_csz(void)
{
	if (CSZ_STREAM_DELTA_DFL % 2 != 0)
		err_panic_puts("csz: internal error");

	uls_init_mutex(&csz_global_mtx);
}

void finalize_csz(void)
{
	reset_csz();
	uls_deinit_mutex(&csz_global_mtx);
}
